﻿using System;
using System.Collections.Generic;
using System.Linq;
using NHibernate.Linq;
using Xenta.Entities;
using Xenta.Enums;
using Xenta.Events;
using Xenta.Utils;

namespace Xenta.Operations
{
    /// <summary>
    /// Exposes the operation interface.
    /// </summary>
    /// <remarks>
    /// The part of framework core. Executes a routine by
    /// using the provided unit of work.
    /// </remarks>
    public interface IOperation : IDependency
    {
        /// <summary>
        /// Executes the operation.
        /// </summary>
        /// <param name="uow">The unit of work.</param>
        void Execute(IUnitOfWork uow);
    }

    /// <summary>
    /// Exposes the operation interface.
    /// </summary>
    /// <typeparam name="TResult">The type of result.</typeparam>
    public interface IOperation<out TResult> : IOperation, 
        IDisclosable
    {
        /// <summary>
        /// Gets the operation result.
        /// </summary>
        TResult Result
        {
            get;
        }
    }

    /// <summary>
    /// Represents the operation base.
    /// </summary>
    public abstract class Operation : IOperation
    {
        #region Methods

        /// <summary>
        /// Executes the operation.
        /// </summary>
        /// <param name="uow">The unit of work.</param>
        public void Execute(IUnitOfWork uow)
        {
            var err = Validate(uow);
            if(err != null)
                throw err.Ex();

            uow.Infrastructure.EventBroker.Publish(
                new PreExecuteOperation(uow, this));
            Work(uow);
            uow.Infrastructure.EventBroker.Publish(
                new PostExecuteOperation(uow, this));
        }

        #endregion

        #region Utilities

        /// <summary>
        /// Performs the operation validation.
        /// </summary>
        /// <param name="uow">The unit of work.</param>
        /// <returns>true, whether the operation is allowed to be executed; otherwise false.</returns>
        protected virtual Err Validate(IUnitOfWork uow)
        {
            return null;
        }

        /// <summary>
        /// Performs the operation logic.
        /// </summary>
        /// <param name="uow">The unit of work.</param>
        protected abstract void Work(IUnitOfWork uow);

        #endregion
    }

    /// <summary>
    /// Represents the operation base.
    /// </summary>
    /// <typeparam name="TResult">The type of result.</typeparam>
    public abstract class Operation<TResult> : Operation, 
        IOperation<TResult>
    {
        #region Fields

        private TResult _result;

        #endregion

        #region Properties

        /// <summary>
        /// Gets the operation result.
        /// </summary>
        public TResult Result
        {
            get
            {
                return _result;
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Discloses the result.
        /// </summary>
        /// <param name="level">The disclosure level.</param>
        /// <param name="options">The disclosure options.</param>
        /// <returns>The result object.</returns>
        public virtual object Disclose(DisclosureLevel level, IDictionary<String, String> options)
        {
            if(!typeof(IDisclosable).IsAssignableFrom(typeof(TResult)))
                return Result;
            var ret = Result as IDisclosable;
            if(ret == null)
                return null;
            return ret.Disclose(level, options);
        }

        #endregion

        #region Utilities

        protected sealed override void Work(IUnitOfWork uow)
        {
            Work(uow, out _result);
        }

        /// <summary>
        /// Performs the operation logic.
        /// </summary>
        /// <param name="uow">The unit of work.</param>
        /// <param name="result">Contains the result, when this method returns.</param>
        protected abstract void Work(IUnitOfWork uow, out TResult result);

        #endregion
    }

    /// <summary>
    /// Represents the operation base.
    /// </summary>
    /// <typeparam name="TEntity">The type of entity.</typeparam>
    /// <typeparam name="TResult">The type of result.</typeparam>
    public abstract class QueryOperation<TEntity, TResult> : Operation<TResult>
        where TEntity : IEntity
    {
        #region Ctors

        /// <summary>
        /// Initializes a new class instance.
        /// </summary>
        protected QueryOperation()
        {
            Fetch = FetchLevel.None;
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the fetch level.
        /// </summary>
        public FetchLevel Fetch
        {
            get;
            set;
        }

        #endregion

        #region Options

        /// <summary>
        /// Gets a value indicates, whether the underlying 
        /// query is cacheable.
        /// </summary>
        protected virtual bool Cacheable
        {
            get
            {
                return false;
            }
        }

        #endregion

        #region Utilities

        protected override void Work(IUnitOfWork uow, out TResult result)
        {
            var query = uow.Query<TEntity>();
            query = Build(uow, query);
            result = Do(uow, query);
        }

        /// <summary>
        /// Builds the query.
        /// </summary>
        /// <param name="uow">The unit of work.</param>
        /// <param name="query">The base query.</param>
        /// <returns>The result query.</returns>
        protected virtual IQueryable<TEntity> Build(IUnitOfWork uow, IQueryable<TEntity> query)
        {
            if(Cacheable)
                query = query.Cacheable();
            return query;
        }

        /// <summary>
        /// Executes the query.
        /// </summary>
        /// <param name="uow">The unit of work.</param>
        /// <param name="query">The query.</param>
        /// <returns>The result.</returns>
        protected abstract TResult Do(IUnitOfWork uow, IQueryable<TEntity> query);

        #endregion
    }
}
